/**
 *  @file UTFCharConv.cpp
 */
#include "UTFCharConv.h"

namespace cppflib
{

namespace text
{

//! start and end of high surrogate
#define U16_HSURRS 0xd800
#define U16_HSURRE 0xdbff
//! start and end of low surrogate
#define U16_LSURRS 0xdc00
#define U16_LSURRE 0xdfff

//! supplementary characters start
#define UNI_SUPCHAR_S 0x10000

//! unicode limit
#define UNI_CODELIMIT 0x10ffff

//! invalid unicode codepoint range
#define UNI_INVCODE_S U16_HSURRS
#define UNI_INVCODE_E U16_LSURRE

//! maximum number of bytes in a utf-8 character
#define MAXUTF8BYTES 6

//! representable range for the number of bytes in a utf-8 char.
PRIVATE const u32_t st_codePointRange[MAXUTF8BYTES][2] = {
                                        {0x00, 0x7f},             // 1 byte
                                        {0x80, 0x7ff},            // 2 byte
                                        {0x800, 0xffff},          // 3 byte
                                        {0x10000, 0x1fffff},      // 4 byte
                                        {0x200000, 0x3ffffff},    // 5 byte
                                        {0x4000000, 0x7fffffff},  // 6 byte
                                      };

/**
 *  Check if a UTF16 char is within the high surrogate range.
 */
_DLLAPI bool IsUTF16HighSurrogate(const wc_t c)
{
   u32_t temp = c;
   return (temp >= U16_HSURRS && temp <= U16_HSURRE);
}

/**
 *  Check if a UTF16 char is within the low surrogate range.
 */
_DLLAPI bool IsUTF16LowSurrogate(const wc_t c)
{
   u32_t temp = c;
   return (temp >= U16_LSURRS && temp <= U16_LSURRE);
}


/**
 *  @return number of bytes possessed by a UTF 8 character by inspecting the first byte
 *  @return -1 if error
 */
_DLLAPI int GetNumBytesForUTF8Char(const char utf8FirstByte)
{
   u32_t temp = utf8FirstByte;   

   if (temp <= 0x7f) {
      return 1;
   }
   else if ((temp & 0xe0) == 0xc0) {
      // 2 bytes
      return 2;
   }
   else if ((temp & 0xf0) == 0xe0) {
      // 3 bytes
      return 3;
   }
   else if ((temp & 0xf8) == 0xf0) {
      // 4 bytes
      return 4;
   }
   else if ((temp & 0xfc) == 0xf8) {
      // 5 bytes
      return 5;
   }
   else if ((temp & 0xfe) == 0xfc) {
      // 6 bytes
      return 6;
   }
   
   // error 
   return -1;
}


/**
 *  Get extra bytes from utf-8 buffer so as to group them into a code point
 *
 *  @param [in, out] pSrc -- source pointer; updated on return
 *  @param [in] pSrcEnd -- end of source
 *  @param [in, out] codePoint -- resultant code point
 *  @param [in] nrExtraBytes -- number of extra bytes to get
 *
 *  @return 0 -- OK
 *  @return < 0 -- error
 */
PRIVATE int __UC_GetUTF8Bytes(u8_t **pSrcStart, u8_t *pSrcEnd, 
                              u32_t *codePoint, int nrExtraBytes)
{
   int i, ret = 0;
   u32_t temp, tPoint;
   u8_t * pSrc = *pSrcStart;

   i = 0;
   tPoint = *codePoint;
   do {
      if (pSrc >= pSrcEnd) {
         ret = -1;
         break;
      }
      temp = *pSrc;
      // must be 0x10xxxxxx
      if ((temp & 0xc0) != 0x80) {
         ret = -1;
         break;
      }

      // increment later so as not to 'eat' the next correct character
      pSrc++;      

      tPoint <<= 6;
      tPoint |= (temp & 0x3f);
      ++i;
   } while(i < nrExtraBytes);

   if (ret == 0) {
      // exceed max unicode
      if (tPoint > UNI_CODELIMIT)
         ret = -1;
      // within surrogate reserved area
      else if (tPoint >= UNI_INVCODE_S && tPoint <= UNI_INVCODE_E)
         ret = -1;
      // exceed representable range
      else if (tPoint < st_codePointRange[nrExtraBytes][0] || tPoint > st_codePointRange[nrExtraBytes][1])
         ret = -1;
   }

   *pSrcStart = pSrc;
   *codePoint = tPoint;
   return ret;
}

/**
 *  Convert utf-8 to utf-16
 *
 *  Input source UTF-8 is the standard UTF-8 encoding but NOT the JAVA style -- 
 *  modified UTF-8. 
 *  Buffer size is expanded on demand if not enough space in pDestBuf.
 *
 *  @param [in] pSrcBuf -- source buffer with utf-8 characters
 *  @param [in] srcLen -- number of utf-8 characters in src buffer
 *  @param [in, out] pDestBuf -- destination buffer to save utf-16 characters;
 *                               NULL if only number of wide characters is interested.
 *  @param [in] destLen -- buffer length of dest buffer in characters
 *
 *  @note invalid characters are ignored silently
 *
 *  @return >= 0 -- number of wide characters is / to be transformed
 *                  (surrogate pairs are 2 characters)
 *  @return < 0 -- error
 */
_DLLAPI int UTF8ToUTF16(const char *pSrcBuf, u32_t srcLen, wc_t *pDestBuf, u32_t destLen)
{
   int ret = 0, isErr = 0;
   u32_t codePoint, temp;   
   u8_t *pSrc = NULL, *pSrcEnd = NULL;
   wc_t *pDest = NULL, *pDestEnd = NULL;
   
   pSrc = reinterpret_cast<u8_t*>(const_cast<char*>(pSrcBuf));
   pSrcEnd = reinterpret_cast<u8_t*>(const_cast<char*>(pSrcBuf)) + srcLen;
   if (pDestBuf) {
      pDest = pDestBuf;
      pDestEnd = pDestBuf + destLen;
   }
   
   while (pSrc < pSrcEnd) {
      isErr = 0;
      temp = *pSrc++;

      //
      // collect utf-8 bytes and group to a code point
      //
      int numBytes = GetNumBytesForUTF8Char(static_cast<char>(temp));
      if (numBytes == 1) {
         codePoint = temp;
      }
      else if (numBytes >= 2) {
         int mask = 0;
         switch (numBytes) {
            case 2: mask = 0x1f; break;
            case 3: mask = 0x0f; break;
            case 4: mask = 0x07; break;
            case 5: mask = 0x03; break;
            case 6: mask = 0x01; break;
         }

         codePoint = temp & mask;
         isErr = __UC_GetUTF8Bytes(&pSrc, pSrcEnd, &codePoint, numBytes - 1);
      }
      else {
         // error 
         isErr = -1;
      }

      
      //
      // convert to UTF-16
      //
      if (isErr == 0) {
         if (pDestBuf) {
            if (codePoint >= UNI_SUPCHAR_S) {
               // surrogate pairs required
            
               if (pDest + 1 >= pDestEnd) {
                  break; // exit loop
               }
               
               // 1) minus 0x10000
               temp = codePoint - UNI_SUPCHAR_S;
               // 2) high 10 bits in first surrogate          
               *pDest++ = static_cast<wc_t>((temp >> 10) + U16_HSURRS);
               // 3) low 10 bits in second surrogate
               *pDest++ = static_cast<wc_t>((temp & 0x3ff) + U16_LSURRS);
            
            }
            else {
               // direct assignment            
               if (pDest >= pDestEnd) {
                  break;  // exit loop
               }

               *pDest++ = static_cast<wc_t>(codePoint);
            }
         }

         // update nrChars
         ret += ((codePoint >= UNI_SUPCHAR_S) ? 2 : 1);
         
      }            

   } // end of while

   
   return ret;
}

/**
 *  Convert a code point to UTF-8 coding
 *
 *  @param [in] codePoint -- code point of the character
 *  @param [out] utf8Array -- output utf-8 string
 *  
 *  @return < 0 -- error
 *  @return >= 0 number of valid bytes in utf8Array
 */
PRIVATE int __UC_CodePoint2UTF8(u32_t codePoint, u8_t utf8Array[MAXUTF8BYTES])
{
   int i, nrBytes = 0;

   // exceed max unicode
   if (codePoint > UNI_CODELIMIT)
      return -1;
   // within surrogate reserved area
   else if (codePoint >= UNI_INVCODE_S && codePoint <= UNI_INVCODE_E)
      return -1;

   for (i = 0 ; i < MAXUTF8BYTES; ++i) {
      if (codePoint >= st_codePointRange[i][0] && codePoint <= st_codePointRange[i][1])
         break;
   }

   nrBytes = i + 1;

   if (nrBytes == 1) {
      // direct assignment
      utf8Array[0] = static_cast<u8_t>(codePoint);
   }
   else {
      // assign from the back to front
      for (i = nrBytes - 1; i >= 1; --i) {
         utf8Array[i] = static_cast<u8_t>(0x80 | (codePoint & 0x3f));
         codePoint >>= 6;
      }

      if (nrBytes == 2) utf8Array[0] = static_cast<u8_t>(0xc0 | (codePoint & 0x1f));
      else if (nrBytes == 3) utf8Array[0] = static_cast<u8_t>(0xe0 | (codePoint & 0x0f));
      else if (nrBytes == 4) utf8Array[0] = static_cast<u8_t>(0xf0 | (codePoint & 0x07));
      else if (nrBytes == 5) utf8Array[0] = static_cast<u8_t>(0xf8 | (codePoint & 0x03));
      else if (nrBytes == 6) utf8Array[0] = static_cast<u8_t>(0xfc | (codePoint & 0x01));
   }
   
   return nrBytes;
}

/**
 *  Convert UTF-16 to UTF-8
 * 
 *  The resultant UTF-8 is in the standard UTF-8 but NOT the JAVA style --
 *  modified UTF-8.
 *  Buffer size is expanded on demand if not enough space in pDestBuf.
 *
 *  @param [in] pSrcBuf -- source buffer with utf-16 characters
 *  @param [in] srcLen -- number of utf-16 characters in source buffer
 *  @param [in, out] pDestBuf -- destination buffer to save utf-8 characters;
 *                               NULL if only number of bytes is interested.
 *  @param [in] destLen -- buffer length of dest buffer in characters
 *
 *  @note invalid characters are ignored silently
 *
 *  @return >= 0 -- number of bytes is / to be transformed
 *  @return < 0 -- error
 */
_DLLAPI int UTF16ToUTF8(const wc_t *pSrcBuf, u32_t srcLen, char *pDestBuf, u32_t destLen)
{
   int i, ret = 0, isErr = 0, nrBytes;
   u32_t codePoint, temp;
   u8_t utf8Array[MAXUTF8BYTES];
   const wc_t *pSrc = NULL, *pSrcEnd = NULL;
   u8_t *pDest = NULL, *pDestEnd = NULL;
   
   pSrc = pSrcBuf;
   pSrcEnd = pSrcBuf + srcLen;
   if (pDestBuf) {
      pDest = reinterpret_cast<u8_t*>(pDestBuf);
      pDestEnd = reinterpret_cast<u8_t*>(pDestBuf + destLen);
   }

   while (pSrc < pSrcEnd) {
      isErr = 0;
      temp = *pSrc++;

      //
      // collect codepoint
      //
      if (temp < U16_HSURRS || (temp > U16_LSURRE && temp < UNI_SUPCHAR_S) ) {
         // simple 2 byte characters
         codePoint = temp;
      }
      else if (temp >= U16_HSURRS && temp <= U16_HSURRE) {
         // surrogate pairs start (high surrogate)
         do {
            if (pSrc >= pSrcEnd) {
               isErr = -1;
               break;
            }
            
            codePoint = (temp - U16_HSURRS) << 10;

            temp = *pSrc;
            // exceed low surrogate range
            if (temp < U16_LSURRS || temp > U16_LSURRE) {
               isErr = -1;
               break;
            }

            // increment later so as not to 'eat' the next correct character
            pSrc++;

            codePoint |= ((temp - U16_LSURRS) & 0x3ff);
            codePoint += UNI_SUPCHAR_S;

         } while(false);
      }
      else {
         isErr = -1;
      }

      //
      // Convert to UTF-8
      //
      if (isErr == 0) {

         nrBytes = __UC_CodePoint2UTF8(codePoint, utf8Array);

         if (nrBytes > 0 && pDestBuf) {
            if (pDest + (nrBytes - 1) >= pDestEnd) {
               break; // exit loop
            }

            // copy to dest buffer
            for (i = 0 ; i < nrBytes; ++i)
               *pDest++ = utf8Array[i];
               
         }

         // update number of bytes
         ret += (nrBytes > 0) ? nrBytes : 0;
      }      

   } // end of while   

   return ret;
}

} // end of namespace text

} // end of namespace cppflib


